{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "IDdZSPcLtKx4"
      },
      "source": [
        "##### Copyright 2019 The TensorFlow Hub Authors.\n",
        "\n",
        "Licensed under the Apache License, Version 2.0 (the \"License\");"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "-g5By3P4tavy"
      },
      "outputs": [],
      "source": [
        "# Copyright 2019 The TensorFlow Hub Authors. All Rights Reserved.\n",
        "#\n",
        "# Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "#     http://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS, \n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License.\n",
        "# =============================================================================="
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "vpaLrN0mteAS"
      },
      "source": [
        "# TF-Hub によるベンガル語の記事分類"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "MfBg1C5NB3X0"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td><a target=\"_blank\" href=\"https://www.tensorflow.org/hub/tutorials/bangla_article_classifier\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\"> TensorFlow.orgで表示</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ja/hub/tutorials/bangla_article_classifier.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\">Run in Google Colab</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://github.com/tensorflow/docs-l10n/blob/master/site/ja/hub/tutorials/bangla_article_classifier.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\">GitHub でソースを表示</a></td>\n",
        "  <td><a href=\"https://storage.googleapis.com/tensorflow_docs/docs-l10n/site/ja/hub/tutorials/bangla_article_classifier.ipynb\"><img src=\"https://www.tensorflow.org/images/download_logo_32px.png\">ノートブックをダウンロード/a0}</a></td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "GhN2WtIrBQ4y"
      },
      "source": [
        "注意: このノートブックでは、pip を用いた Python パッケージのインストールに加え、`sudo apt install`を使用してシステムパッケージをインストールします。これには`unzip`を使います。\n",
        "\n",
        "この Colab は、非英語/現地語のテキスト分類に [Tensorflow Hub](https://www.tensorflow.org/hub/) を使用したデモンストレーションです。ここではローカル言語として [ベンガル語](https://en.wikipedia.org/wiki/Bengali_language) を選択し、事前トレーニングされた単語埋め込みを使用してベンガル語のニュース記事を 5 つのカテゴリに分類する、マルチクラス分類タスクを解決します。ベンガル語の事前トレーニング済みの単語埋め込みは [fastText](https://fasttext.cc/docs/en/crawl-vectors.html) を使用します。これは Facebook のライブラリで、157 言語の事前トレーニング済みの単語ベクトルが公開されています。\n",
        "\n",
        "ここでは TF-Hub (TensorFlow Hub) の事前トレーニング済みの埋め込みエクスポート機能を使用して、まず単語埋め込みをテキスト埋め込みモジュールに変換した後、そのモジュールを使用して Tensorflow の使いやすい高レベル API である [tf.keras](https://www.tensorflow.org/api_docs/python/tf/keras) で分類器のトレーニングを行い、ディープラーニングモデルを構築します。ここでは fastText Embedding を使用していますが、他のタスクで事前トレーニングした別の埋め込みをエクスポートし、TensorFlow Hub で素早く結果を得ることも可能です。 "
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Q4DN769E2O_R"
      },
      "source": [
        "## セットアップ"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "9Vt-StAAZguA"
      },
      "outputs": [],
      "source": [
        "%%bash\n",
        "# https://github.com/pypa/setuptools/issues/1694#issuecomment-466010982\n",
        "pip install gdown --no-use-pep517"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "WcBA19FlDPZO"
      },
      "outputs": [],
      "source": [
        "%%bash\n",
        "sudo apt-get install -y unzip"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "zSeyZMq-BYsu"
      },
      "outputs": [],
      "source": [
        "import os\n",
        "\n",
        "import tensorflow as tf\n",
        "import tensorflow_hub as hub\n",
        "\n",
        "import gdown\n",
        "import numpy as np\n",
        "from sklearn.metrics import classification_report\n",
        "import matplotlib.pyplot as plt\n",
        "import seaborn as sns"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "9FB7gLU4F54l"
      },
      "source": [
        "# データセット\n",
        "\n",
        "ここで使用するのは [BARD](https://www.researchgate.net/publication/328214545_BARD_Bangla_Article_Classification_Using_a_New_Comprehensive_Dataset)（ベンガル語記事データセット）です。これは、様々なベンガル語のニュースポータルから収集した約 3,76,226 件の記事が、経済、州、国際、スポーツ、エンターテイメントの 5 つのカテゴリに分類されています。ファイルは Google Drive からダウンロードしますが、[bit.ly/BARD_DATASET](bit.ly/BARD_DATASET) のリンクは[この](https://github.com/tanvirfahim15/BARD-Bangla-Article-Classifier) GitHub リポジトリから参照しています。\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "zdQrL_rwa-1K"
      },
      "outputs": [],
      "source": [
        "gdown.download(\n",
        "    url='https://drive.google.com/uc?id=1Ag0jd21oRwJhVFIBohmX_ogeojVtapLy',\n",
        "    output='bard.zip',\n",
        "    quiet=True\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "P2YW4GGa9Y5o"
      },
      "outputs": [],
      "source": [
        "%%bash\n",
        "unzip -qo bard.zip"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "js75OARBF_B8"
      },
      "source": [
        "# 事前トレーニング済み単語ベクトルを TF-Hub モジュールにエクスポートする"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "-uAicYA6vLsf"
      },
      "source": [
        "TF-Hub には、単語埋め込みを TF-Hubの テキスト埋め込みモジュールに変換する、[この](https://github.com/tensorflow/hub/tree/master/examples/text_embeddings_v2)便利なスクリプトがあります。export_v2.py と同じディレクトリに単語埋め込み用の .txt または .vec ファイルをダウンロードしてスクリプトを実行するだけで、ベンガル語やその他の言語用のモジュールを作成することができます。\n",
        "\n",
        "エクスポーターは埋め込みベクトルを読み込んで、Tensorflow の [SavedModel](https://www.tensorflow.org/beta/guide/saved_model) にエクスポートします。SavedModel には重みとグラフを含む完全な TensorFlow プログラムが含まれています。TF-Hub は SavedModel を[モジュール](https://www.tensorflow.org/hub/api_docs/python/hub/Module)として読み込むことができます。モデルを構築には tf.keras を使用するので、ハブモジュールにラッパーを提供する [hub.KerasLayer](https://www.tensorflow.org/hub/api_docs/python/hub/KerasLayer) を用いて Keras のレイヤーとして使用します。\n",
        "\n",
        "まず、fastText から単語埋め込みを、TF-Hub の[レポジトリ](https://github.com/tensorflow/hub)から埋め込みエクスポーターを取得します。\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "5DY5Ze6pO1G5"
      },
      "outputs": [],
      "source": [
        "%%bash\n",
        "curl -O https://dl.fbaipublicfiles.com/fasttext/vectors-crawl/cc.bn.300.vec.gz\n",
        "curl -O https://raw.githubusercontent.com/tensorflow/hub/master/examples/text_embeddings_v2/export_v2.py\n",
        "gunzip -qf cc.bn.300.vec.gz --k"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "PAzdNZaHmdl1"
      },
      "source": [
        "次に、エクスポートスクリプトを埋め込みファイル上で実行します。fastText Embedding にはヘッダ行があり、かなり大きい（ベンガル語でモジュール変換後 3.3GB 程度）ため、ヘッダ行を無視して最初の 100,000 トークンのみをテキスト埋め込みモジュールにエクスポートします。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Tkv5acr_Q9UU"
      },
      "outputs": [],
      "source": [
        "%%bash\n",
        "python export_v2.py --embedding_file=cc.bn.300.vec --export_path=text_module --num_lines_to_ignore=1 --num_lines_to_use=100000"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "k9WEpmedF_3_"
      },
      "outputs": [],
      "source": [
        "module_path = \"text_module\"\n",
        "embedding_layer = hub.KerasLayer(module_path, trainable=False)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "fQHbmS_D4YIo"
      },
      "source": [
        "テキスト埋め込みモジュールは、文字列の 1 次元テンソル内の文のバッチを入力として受け取り、文に対応する形状の埋め込みベクトル (batch_size, embedding_dim) を出力します。これは入力をスペースで分割して、前処理を行います。単語埋め込みは `sqrtn` コンバイナ（[こちらを](https://www.tensorflow.org/api_docs/python/tf/nn/embedding_lookup_sparse)参照）を使用して文の埋め込みに結合されます。これの実演として、ベンガル語の単語リストを入力として渡し、対応する埋め込みベクトルを取得します。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Z1MBnaBUihWn"
      },
      "outputs": [],
      "source": [
        "embedding_layer(['বাস', 'বসবাস', 'ট্রেন', 'যাত্রী', 'ট্রাক']) "
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "4KY8LiFOHmcd"
      },
      "source": [
        "# Tensorflow Dataset を変換する\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "pNguCDNe6bvz"
      },
      "source": [
        "データセットが非常に大きいため、データセット全体をメモリに読み込むのではなく、[Tensorflow Dataset](https://www.tensorflow.org/api_docs/python/tf/data/Dataset) の機能を利用してジェネレータを使用し、実行時にサンプルをバッチで生成します。また、データセットは非常にバランスが悪いので、ジェネレータを使用する前にデータセットをシャッフルします。\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "bYv6LqlEChO1"
      },
      "outputs": [],
      "source": [
        "dir_names = ['economy', 'sports', 'entertainment', 'state', 'international']\n",
        "\n",
        "file_paths = []\n",
        "labels = []\n",
        "for i, dir in enumerate(dir_names):\n",
        "  file_names = [\"/\".join([dir, name]) for name in os.listdir(dir)]\n",
        "  file_paths += file_names\n",
        "  labels += [i] * len(os.listdir(dir))\n",
        "  \n",
        "np.random.seed(42)\n",
        "permutation = np.random.permutation(len(file_paths))\n",
        "\n",
        "file_paths = np.array(file_paths)[permutation]\n",
        "labels = np.array(labels)[permutation]"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8b-UtAP5TL-W"
      },
      "source": [
        "シャッフル後には、トレーニング例と検証例のラベルの分布を確認することができます。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "mimhWVSzzAmS"
      },
      "outputs": [],
      "source": [
        "train_frac = 0.8\n",
        "train_size = int(len(file_paths) * train_frac)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "4BNXFrkotAYu"
      },
      "outputs": [],
      "source": [
        "# plot training vs validation distribution\n",
        "plt.subplot(1, 2, 1)\n",
        "plt.hist(labels[0:train_size])\n",
        "plt.title(\"Train labels\")\n",
        "plt.subplot(1, 2, 2)\n",
        "plt.hist(labels[train_size:])\n",
        "plt.title(\"Validation labels\")\n",
        "plt.tight_layout()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "RVbHb2I3TUNA"
      },
      "source": [
        "ジェネレータを使用して[データセット](https://www.tensorflow.org/api_docs/python/tf/data/Dataset)を作成するには、まず file_paths から各項目を、ラベル配列からラベルを読み込むジェネレータ関数を書き込み、各ステップ毎にそれぞれ 1 つのトレーニング例を生成します。このジェネレータ関数を [tf.data.Dataset.from_generator](https://www.tensorflow.org/api_docs/python/tf/data/Dataset#from_generator) メソッドに渡して出力タイプを指定します。各トレーニング例は、tf.string データ型の項目と One-Hot エンコーディングされたラベルを含むタプルです。[`skip`](https://www.tensorflow.org/api_docs/python/tf/data/Dataset#skip)メソッドと[`take`](https://www.tensorflow.org/api_docs/python/tf/data/Dataset#take) メソッドを使用して、データセットは 80 対 20 の割合でトレーニングデータと検証データに分割しています。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "eZRGTzEhUi7Q"
      },
      "outputs": [],
      "source": [
        "def load_file(path, label):\n",
        "    return tf.io.read_file(path), label"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "2g4nRflB7fbF"
      },
      "outputs": [],
      "source": [
        "def make_datasets(train_size):\n",
        "  batch_size = 256\n",
        "\n",
        "  train_files = file_paths[:train_size]\n",
        "  train_labels = labels[:train_size]\n",
        "  train_ds = tf.data.Dataset.from_tensor_slices((train_files, train_labels))\n",
        "  train_ds = train_ds.map(load_file).shuffle(5000)\n",
        "  train_ds = train_ds.batch(batch_size).prefetch(tf.data.experimental.AUTOTUNE)\n",
        "\n",
        "  test_files = file_paths[train_size:]\n",
        "  test_labels = labels[train_size:]\n",
        "  test_ds = tf.data.Dataset.from_tensor_slices((test_files, test_labels))\n",
        "  test_ds = test_ds.map(load_file)\n",
        "  test_ds = test_ds.batch(batch_size).prefetch(tf.data.experimental.AUTOTUNE)\n",
        "\n",
        "\n",
        "  return train_ds, test_ds"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "8PuuN6el8tv9"
      },
      "outputs": [],
      "source": [
        "train_data, validation_data = make_datasets(train_size)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "MrdZI6FqPJNP"
      },
      "source": [
        "# モデルのトレーニングと評価"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "jgr7YScGVS58"
      },
      "source": [
        "既にモジュールの周りにラッパーを追加し、Keras の他のレイヤーと同じように使用できるようになったので、レイヤーの線形スタックである小さな [Sequential](https://www.tensorflow.org/api_docs/python/tf/keras/Sequential) モデルを作成します。他のレイヤーと同様に `model.add` を使用して、テキスト埋め込みモジュールの追加が可能です。損失とオプティマイザを指定してモデルをコンパイルし、10 エポック分をトレーニングします。`tf.keras` API はテンソルフローのデータセットを入力として扱うことができるので、fit メソッドに Dataset インスタンスを渡してモデルをトレーニングすることができます。ジェネレータ関数を使用するので、tf.data がサンプルの生成、バッチ処理、モデルへの供給を行います。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "WhCqbDK2uUV5"
      },
      "source": [
        "## モデル"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "nHUw807XPPM9"
      },
      "outputs": [],
      "source": [
        "def create_model():\n",
        "  model = tf.keras.Sequential([\n",
        "    tf.keras.layers.Input(shape=[], dtype=tf.string),\n",
        "    embedding_layer,\n",
        "    tf.keras.layers.Dense(64, activation=\"relu\"),\n",
        "    tf.keras.layers.Dense(16, activation=\"relu\"),\n",
        "    tf.keras.layers.Dense(5),\n",
        "  ])\n",
        "  model.compile(loss=tf.losses.SparseCategoricalCrossentropy(from_logits=True),\n",
        "      optimizer=\"adam\", metrics=['accuracy'])\n",
        "  return model"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "5J4EXJUmPVNG"
      },
      "outputs": [],
      "source": [
        "model = create_model()\n",
        "# Create earlystopping callback\n",
        "early_stopping_callback = tf.keras.callbacks.EarlyStopping(monitor='val_loss', min_delta=0, patience=3)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ZZ7XJLg2u2No"
      },
      "source": [
        "## トレーニング"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "OoBkN2tAaXWD"
      },
      "outputs": [],
      "source": [
        "history = model.fit(train_data, \n",
        "                    validation_data=validation_data, \n",
        "                    epochs=5, \n",
        "                    callbacks=[early_stopping_callback])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "XoDk8otmMoT7"
      },
      "source": [
        "## 評価"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "G5ZRKGOsXEh4"
      },
      "source": [
        "`fit` メソッドが返す各エポックの損失と精度の値を含む `history` オブジェクトを使用して、学習データと検証データの精度と損失曲線を可視化することができます。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "V6tOnByIOeGn"
      },
      "outputs": [],
      "source": [
        "# Plot training & validation accuracy values\n",
        "plt.plot(history.history['accuracy'])\n",
        "plt.plot(history.history['val_accuracy'])\n",
        "plt.title('Model accuracy')\n",
        "plt.ylabel('Accuracy')\n",
        "plt.xlabel('Epoch')\n",
        "plt.legend(['Train', 'Test'], loc='upper left')\n",
        "plt.show()\n",
        "\n",
        "# Plot training & validation loss values\n",
        "plt.plot(history.history['loss'])\n",
        "plt.plot(history.history['val_loss'])\n",
        "plt.title('Model loss')\n",
        "plt.ylabel('Loss')\n",
        "plt.xlabel('Epoch')\n",
        "plt.legend(['Train', 'Test'], loc='upper left')\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "D54IXLqcG8Cq"
      },
      "source": [
        "## 予測\n",
        "\n",
        "検証データの予測値を取得して混同行列をチェックすることにより、5 つの各クラスのモデルの性能を確認することができます。`predict` メソッドが各クラスの確率として N-D 配列を返すので、`np.argmax` を使用してそれらをクラスラベルに変換します。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "dptEywzZJk4l"
      },
      "outputs": [],
      "source": [
        "y_pred = model.predict(validation_data)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "7Dzeml6Pk0ub"
      },
      "outputs": [],
      "source": [
        "y_pred = np.argmax(y_pred, axis=1)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "T4M3Lzg8jHcB"
      },
      "outputs": [],
      "source": [
        "samples = file_paths[0:3]\n",
        "for i, sample in enumerate(samples):\n",
        "  f = open(sample)\n",
        "  text = f.read()\n",
        "  print(text[0:100])\n",
        "  print(\"True Class: \", sample.split(\"/\")[0])\n",
        "  print(\"Predicted Class: \", dir_names[y_pred[i]])\n",
        "  f.close()\n",
        "  "
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "PlDTIpMBu6h-"
      },
      "source": [
        "## パフォーマンスを比較する\n",
        "\n",
        "これで `labels`から検証データの正しいラベルを得ることができるようになったので、それを予測と比較して [classification_report](http://scikit-learn.org/stable/modules/generated/sklearn.metrics.classification_report.html) を取得します。 "
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "mqrERUCS1Xn7"
      },
      "outputs": [],
      "source": [
        "y_true = np.array(labels[train_size:])"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "NX5w-NuTKuVP"
      },
      "outputs": [],
      "source": [
        "print(classification_report(y_true, y_pred, target_names=dir_names))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "p5e9m3bV6oXK"
      },
      "source": [
        "また、発表されたオリジナルの[論文](https://www.researchgate.net/publication/328214545_BARD_Bangla_Article_Classification_Using_a_New_Comprehensive_Dataset)で結果として報告されている精度 0.96 とモデルの性能を比較することもできます。オリジナルの論文の著者は、句読点や数字を削除したり、最も頻繁に使われるストップワードの上位 25 個を削除したり、データセットに対して多くの前処理を行ったと説明しています。classification_report を見ると分かりますが、ここでは前処理を行わずに 5 エポック分のトレーニングを行っただけでも、0.96 の精度と正解率が得られています！\n",
        "\n",
        "この例では、埋め込みモジュールから Keras レイヤーを作成する際に`trainable=False`を設定しました。つまり、トレーニング中に埋め込み重みを更新しないことを意味します。これを True 設定にしてこのデータセットでトレーニングを行ってみると、わずか 2 エポックで 97% の精度を達成します。 "
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [
        "IDdZSPcLtKx4"
      ],
      "name": "bangla_article_classifier.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
